package com.mingyuean.demo.mybatis.session.defaults;

import com.mingyuean.demo.mybatis.mapping.BoundSql;
import com.mingyuean.demo.mybatis.mapping.Environment;
import com.mingyuean.demo.mybatis.mapping.MappedStatement;
import com.mingyuean.demo.mybatis.session.Configuration;
import com.mingyuean.demo.mybatis.session.SqlSession;
import com.mingyuean.demo.mybatis.type.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.*;

/**
 * @author MingYueAn
 * <p>
 * <p>  2023/3/15 13:46
 * @version: 1.0
 */
public class DefaultSqlSession implements SqlSession {

    private static final Logger log = LoggerFactory.getLogger(DefaultSqlSession.class);

    private final Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <T> T getMapper(Class<T> mapperClass) {
        return configuration.getMapper(mapperClass, this);
    }

    @Override
    public Configuration getConfiguration() {
        return configuration;
    }

    @Override
    public <T> T selectOne(String namespaceId, Object parameter) {
        log.debug("sql语句的标识 namespace+id = {}", namespaceId);
        try {
            // 根据命名空间ID获取对应的 MappedStatement 对象
            final MappedStatement mappedStatement = configuration.getMappedStatement(namespaceId);
            // 获取 MyBatis 的配置环境对象
            final Environment environment = configuration.getEnvironment();
            // 获取数据库连接对象
            final Connection connection = environment.getDataSource().getConnection();
            // 获取查询语句的 BoundSql 对象
            final BoundSql boundSql = mappedStatement.getBoundSql();
            log.debug("未完待续 boundSql = {}", boundSql.getSql());
            // 创建 PreparedStatement 对象，并将查询语句绑定到预编译的 SQL 语句中
            final PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
            // 将预处理语句中的占位符?用值替换
            // TODO: 2023/3/23 未完待续
            preparedStatement.setLong(1, Long.parseLong(((Object[]) parameter)[0].toString()));
            if (preparedStatement.execute()) {
                // 执行查询操作，获取 ResultSet 对象
                final ResultSet resultSet = preparedStatement.getResultSet();
                // 结果集处理
                List<T> objList = resultSet2Obj(resultSet, Class.forName(boundSql.getResultType()));
                if (objList.size() == 1) {
                    return objList.get(0);
                } else {
                    throw new RuntimeException("selectOne（）应返回一个结果（或null），但找到： " + objList.size());
                }
            }
            throw new RuntimeException("未执行sql语句");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    // TODO: 2023/3/20 未完待续------------------------
    /**
     * 类型处理容器
     */
    private static final Map<Class<?>, TypeHandler<?>> HANDLER_HASH_MAP = new HashMap<>();

    static {
        HANDLER_HASH_MAP.put(String.class, new StringTypeHandler());
        HANDLER_HASH_MAP.put(Long.class, new LongTypeHandler());
        HANDLER_HASH_MAP.put(Integer.class, new IntegerTypeHandler());
        HANDLER_HASH_MAP.put(Date.class, new DateTypeHandler());
    }

    private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
        // 返回的结果
        final List<Object> objectList = new ArrayList<>();
        //=========================
        if (clazz.getTypeParameters().length > 0) {
            // 如果是泛型类型，获取其不是泛型的父类或接口的 Class 对象
            Type type = clazz.getGenericSuperclass();
            // https://blog.csdn.net/sageyin/article/details/114701550
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                clazz = (Class<?>) actualTypeArguments[0];
            }
        }
        log.debug("实际类型：clazz = {}", clazz);
        //=========================
        // 获取结果集中所有字段
        final ArrayList<String> columnList = new ArrayList<>();
        try {
            // 获取结果集的元数据
            final ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 0; i < metaData.getColumnCount(); i++) {
                columnList.add(metaData.getColumnName(i + 1));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.debug("结果集全部字段：{}", columnList);
        //==============================
        // 存放set方法的集合
        final HashMap<String, Method> setterMethodMap = new HashMap<>(columnList.size());
        for (Method declaredMethod : clazz.getDeclaredMethods()) {
            // 判断方法名是否是以set开头
            if (declaredMethod.getName().startsWith("set")) {
                // 获取属性名
                final String propertyName = declaredMethod.getName().substring(3);
                // String.toLowerCase(Locale.ROOT)：https://blog.csdn.net/qq_45425667/article/details/115245107
                final String name = propertyName.substring(0, 1).toLowerCase(Locale.ROOT) + propertyName.substring(1);
                setterMethodMap.put(name, declaredMethod);
            }
        }
        log.debug("setter方法的集合：{}", setterMethodMap);
        //=========================
        try {
            // 结果集处理
            while (resultSet.next()) {
                // 构造方法创建对象
                final Object obj = clazz.newInstance();
                // 通过结果集的字段调用set方法
                for (final String column : columnList) {
                    // 从set方法集合中获取column对应的方法
                    final Method setterMethod = setterMethodMap.get(column);
                    // 根据方法的参数类型获取结果
                    setterMethod.invoke(obj, HANDLER_HASH_MAP
                            .get(setterMethod.getParameterTypes()[0])
                            .getResult(resultSet, column));
                }
                objectList.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (List<T>) objectList;
    }
}
